﻿package we3d.core.transform 
{
	import we3d.math.Matrix3d;
	import we3d.math.Vector3d;
	
	/**
	* Transform3d is the base and also the default transform object. It provides only position and rotation features.
	*/
	public class Transform3d 
	{
		public function Transform3d () {}
		
		/**
		* @private
		*/
		public var gv:Matrix3d = new Matrix3d();
		/**
		* @private
		*/
		public var _parent:Transform3d = null;
		/**
		* @private
		*/
		public static var FCounter:int=0;
		
		private var _rotationX:Number=0;
		private var _rotationY:Number=0;
		private var _rotationZ:Number=0;
		
		/**
		* Get or set the transform
		*/
		public function get transform () :Matrix3d {
			return gv;
		}
		public function set transform (t:Matrix3d) :void {
			var m:Matrix3d = gv;
			m.a = t.a;	m.b = t.b;	m.c = t.c;
			m.e = t.e;	m.f = t.f;	m.g = t.g;
			m.i = t.i;	m.j = t.j;	m.k = t.k;
			m.m = t.m;	m.n = t.n;	m.o = t.o;
			updateRotation(m);
		}
		
		/**
		* Position on X axis
		*/
		public function get x () :Number {
			return transform.m;
		}
		public function set x (v:Number) :void {
			transform.m = v;
		}
		
		/**
		* Position on Y axis
		*/
		public function get y () :Number {
			return transform.n;
		}
		public function set y (v:Number) :void {
			transform.n = v;
		}
		
		/**
		* Position on Z axis
		*/
		public function get z () :Number {
			return transform.o;
		}
		public function set z (v:Number) :void {
			transform.o = v;
		}
		
		/**
		* Set position on all three axis at once
		* @param	ax
		* @param	ay
		* @param	az
		*/
		public function setPosition (ax:Number=0, ay:Number=0, az:Number=0) :void {
			var tr:Matrix3d = transform;
			tr.m = ax;
			tr.n = ay;
			tr.o = az;
		}
		
		/**
		* Move the location
		* @param	axis unit length vector
		* @param	val offset to move on the axis
		*/
		public function moveOnAxis (axis:Vector3d, val:Number) :void {
			var tr:Matrix3d = transform;
			tr.m += axis.x*val;
			tr.n += axis.y*val;
			tr.o += axis.z*val;
		}
		
		/**
		* Rotation X
		*/
		public function get rotationX () :Number {
			return _rotationX;
		}
		public function set rotationX (v:Number) :void {
			_rotationX = v;
			initAxis();
		}
		
		/**
		* Rotation Y
		*/
		public function get rotationY () :Number {
			return _rotationY;
		}
		public function set rotationY (v:Number) :void {
			_rotationY = v;
			initAxis();
		}
		
		/**
		* Rotation Z
		*/
		public function get rotationZ () :Number {
			return _rotationZ;
		}
		public function set rotationZ (v:Number) :void {
			_rotationZ = v;
			initAxis();
		}
		
		/**
		* Set the rotation on all three axis at once. This is much faster then calling three setters for XY and Z rotation.
		* @param	ax
		* @param	ay
		* @param	az
		*/
		public function setRotation (ax:Number=0, ay:Number=0, az:Number=0) :void {
			_rotationX = ax;
			_rotationY = ay;
			_rotationZ = az;
			initAxis();
		}
		
		/**
		* Rotates the transform on an axis.
		* @param	axis unit length vector
		* @param	r angle in radian
		*/
		public function rotateOnAxis (axis:Vector3d, r:Number) :void {
			
			if(Math.abs(r) < 0.0005) return;
			
			var x:Number = axis.x;
			var y:Number = axis.y;
			var z:Number = axis.z;
			
			var _m:Number = Math.sqrt(x*x+y*y+z*z);
			x /= _m; y /= _m; z /= _m;
			
			var s:Number = Math.sin(r);
			var _c:Number = Math.cos(r);
			var u:Number = 1-_c;
			var sx:Number = s*x;	var sy:Number = s*y;	var sz:Number = s*z;
			var xy:Number = y*x*u;	var zy:Number = y*z*u;	var xz:Number = z*x*u;
			
			var mata:Number = x*x* u + _c;	var matb:Number = xy + sz;		var matc:Number = xz - sy;
			var mate:Number = xy - sz;		var matf:Number = y*y* u + _c;	var matg:Number = zy + sx;
			var mati:Number = xz + sy;		var matj:Number = zy - sx;		var matk:Number = z*z* u + _c;
			
			var tr:Matrix3d = transform;
			var ta:Number = tr.a;	var tb:Number = tr.b;	var tc:Number = tr.c;
			var te:Number = tr.e;	var tf:Number = tr.f;	var tg:Number = tr.g;
			var ti:Number = tr.i;	var tj:Number = tr.j;	var tk:Number = tr.k;
			
			tr.a = ta*mata + tb*mate + tc*mati;
			tr.b = ta*matb + tb*matf + tc*matj;
			tr.c = ta*matc + tb*matg + tc*matk;
				
			tr.e = te*mata + tf*mate + tg*mati;
			tr.f = te*matb + tf*matf + tg*matj;
			tr.g = te*matc + tf*matg + tg*matk;
				
			tr.i = ti*mata + tj*mate + tk*mati;
			tr.j = ti*matb + tj*matf + tk*matj;
			tr.k = ti*matc + tj*matg + tk*matk;
			
			updateRotation(tr);
		}
		
		/**
		* @private
		*/
		protected function updateRotation (r:Matrix3d) :void {
			var tx:Number = Math.asin(r.j);
			_rotationY = 0;
			_rotationX = -tx;
			if(tx < Math.PI/2) {
				if(tx > -Math.PI/2) {
					_rotationZ = -Math.atan2(-r.b, r.f);
					_rotationY = -Math.atan2(-r.i, r.k);
				}else{
					_rotationZ = Math.atan2(r.b, r.a);
				}
			}else{
				_rotationZ = -Math.atan2(r.c, r.a);
			}
		}
		
		/**
		* @private
		*/
		public function initAxis () :void {
			
			var ax:Number = -_rotationX;
			var ay:Number =  _rotationY;
			var az:Number = -_rotationZ;
			
			var cx:Number = Math.cos(ax);
			var sx:Number = Math.sin(ax);
			var cy:Number = Math.cos(ay);
			var sy:Number = Math.sin(ay);
			var cz:Number = Math.cos(az);
			var sz:Number = Math.sin(az);
			
			var tr:Matrix3d = transform;
			tr.a = cz*cy-sz*-sx*sy;
			tr.b = -sz*cx;
			tr.c = cz*-sy-sz*-sx*cy;
			
			tr.e = sz*cy+cz*-sx*sy;
			tr.f = cz*cx;
			tr.g = sz*-sy+cz*-sx*cy;
			
			tr.i = cx*sy;
			tr.j = sx;
			tr.k = cx*cy;
		}
		
		/**
		* Local x axis
		*/
		public function get xAxis () :Vector3d {
			var tr:Matrix3d = transform;
			return new Vector3d(tr.a, tr.b, tr.c);
		}
		
		/**
		* Local y axis
		*/
		public function get yAxis () :Vector3d {	
			var tr:Matrix3d = transform;
			return new Vector3d(tr.e, tr.f, tr.g);
		}
		
		/**
		* Local z axis
		*/
		public function get zAxis () :Vector3d {
			var tr:Matrix3d = transform;
			return new Vector3d(tr.i, tr.j, tr.k);
		}
		
		/**
		* Reset the position and rotation to zero
		*/
		public function reset () :void {
			var tr:Matrix3d = transform;
			tr.initialize();
			_rotationX = _rotationY = _rotationZ = 0;
		}
		
		/**
		* @private
		*/
		public function initFrame (f:Number) :void {}
		
		/**
		* Returns the position in world coordinate as a new Vector
		*/
		public function get worldPosition () :Vector3d {
			return new Vector3d(gv.m, gv.n, gv.o);
		}
		
		public function addChild (c:Transform3d) :void {
			c._parent = this;
		}
		public function removeChild (c:Transform3d) :Boolean {
			c._parent = null;
			return false; 
		}
	}
}